import string,cgi,time
from collections import deque
import random
import urlparse
import uuid
import logging
import wsgiref.handlers
import pickle
from os import path;

try:
    from urlparse import parse_qs, parse_qsl
except ImportError:
    # fall back for Python 2.5 
    from cgi import parse_qs, parse_qsl
    
SILENCE = 0; 
NOT_BUSY = 0;
MAX_USER = 5000;   
ELAPSE_PERIOD = 3600 * 24 * 7 * 2;
CLEAN_MEMORY_USER_THRESHOLD = 2000;
CLEAN_INTERVAL = 3600 * 2;
    
class User:
    def __init__(self,number, token):
        self.phoneNumber_ = number
        self.connectTo_ = -1;
        self.messageQueue_ = deque("");
        self.token_ = token;
        self.ring_ = 0
        self.busy_ = 0
        self.lastAccessTime_ = time.time();
    def __del__(self):    
        pass		
        
    def id(self):
        return self.phoneNumber_;

    def token(self):
        return self.token_;
        
    def hasNewMessage(self):
        return (len(self.messageQueue_) >= 1)

    def read(self):
        msg = "";
        if (len(self.messageQueue_) >= 1):
            UserCollection.getInstance().notifyContentChanged();
            msg += self.readSingleMsg();
        if (msg != ""):
            UserCollection.getInstance().notifyContentChanged();
            self.lastAccessTime_ = time.time();    
        return msg;    
        
    def readSingleMsg(self):
        if (len(self.messageQueue_) >= 1):
            return self.messageQueue_.popleft()   

    def write(self, msg):
        if (msg != ""):
            UserCollection.getInstance().notifyContentChanged();
            self.messageQueue_.append(msg)
            self.lastAccessTime_ = time.time();   
        
    def ring(self,callingFrom):
        UserCollection.getInstance().notifyContentChanged();
        self.ring_ = callingFrom
        self.lastAccessTime_ = time.time();   
     
    def fetchRing(self):
        global SILENCE
        ring =  self.ring_
        self.ring_ = SILENCE
        if (ring != SILENCE):
            UserCollection.getInstance().notifyContentChanged();
        return ring
        
    def notifyBusy(self, sourceLine):
        UserCollection.getInstance().notifyContentChanged();
        self.busy_ = sourceLine
        self.lastAccessTime_ = time.time();   
    
    def fetchNotifyBusy(self):
        global NOT_BUSY
        busy = self.busy_
        self.busy_ = NOT_BUSY;
        if (busy != NOT_BUSY):
            UserCollection.getInstance().notifyContentChanged();
        return busy;
    
    def keepAlive(self):
        self.lastAccessTime_ = time.time(); 
    
    def isDead(self):
        global ELAPSE_PERIOD
        if (time.time() - self.lastAccessTime_ > ELAPSE_PERIOD):
            return True;
        else:
            return False;
            
class UserCollection:
    __g_userCollection__ = None;
    
    @staticmethod
    def getInstance():    
        if (UserCollection.__g_userCollection__ == None):
            UserCollection.__g_userCollection__ = UserCollection();
        return UserCollection.__g_userCollection__;

    @staticmethod
    def notCreatedYet():
        if (UserCollection.__g_userCollection__ == None):
            return True;
        else:
            return False;

    @staticmethod
    def marshal():
        return pickle.dumps(UserCollection.__g_userCollection__)
        
    @staticmethod
    def unMarshal(str):
        UserCollection.__g_userCollection__ = pickle.loads(str)
        return UserCollection.getInstance();
    
    def __init__(self): 
        self.userMap_ = dict();
        self.junctionBox_ = dict();
        self.lastClean_ = time.time();
        self.lastCleanAll_ = time.time();
        self.contentChanged_ = False;
        self.version_=0;
       
    def count(self):
        return len(self.userMap_);
        
    def isFull(self):
        global MAX_USER;
        if (len(self.userMap_) >= MAX_USER):
            return True;
        else:
            return False;
        
    def createUser(self, id, token):
        self.userMap_[id] = User(id, token);
        self.notifyContentChanged();
        return self.userMap_[id]
     
    def getUser(self,id):
        if (self.userMap_.has_key(id)):
            return self.userMap_[id];
            
    def hasUser(self,id):
        if (self.userMap_.has_key(id)):
            return True;
        else:
            return False;
            
    def fixup(self, line):
        return self.doFixup(line, line)
        
    def doFixup(self, line, startLine):
        if (self.junctionBox_.has_key(line)):
            toLine = self.junctionBox_[line]
            if(not self.userMap_.has_key(line) or not self.userMap_.has_key(toLine) ):
                self.notifyContentChanged();
                self.junctionBox_.pop(line);
            if (toLine != startLine):
                self.doFixup(toLine, startLine);     
                
    def wire(self, fromLine, toLine):
        self.notifyContentChanged();
        self.unWire(fromLine);
        self.unWire(toLine);
        if (not self.userMap_.has_key(fromLine)):
            return False;
        if (not self.userMap_.has_key(toLine)):
            return False;    
        self.junctionBox_[fromLine] = toLine
        self.junctionBox_[toLine] == fromLine
        return True;
        
    def oneWayWire(self, fromLine, toLine):
        self.notifyContentChanged();
        self.unWire(fromLine);
        if (not self.userMap_.has_key(fromLine)):
            return False;
        if (not self.userMap_.has_key(toLine)):
            return False;    
        self.junctionBox_[fromLine] = toLine
        return True;    
        
    def unWire(self, fromLine):
        self.notifyContentChanged();
        self.fixup(fromLine);
        if (self.junctionBox_.has_key(fromLine)):
            toLine = self.junctionBox_[fromLine];
            self.junctionBox_.pop(fromLine);
            if (self.junctionBox_.has_key(toLine)):
                if (self.junctionBox_[toLine] == fromLine):
                    self.junctionBox_.pop(toLine);
        return True;
        
        
    def doIsWired(self, fromLine, toLine):
        self.fixup(fromLine);
        self.fixup(toLine);
        if (not self.junctionBox_.has_key(fromLine)):
            return False;
        if (not self.junctionBox_.has_key(toLine)):
            return False;    
        if (self.junctionBox_[fromLine] == toLine and self.junctionBox_[toLine] == fromLine):
            return True;
        return False;
        
    def isWired(self, fromLine):
        self.fixup(fromLine);
        if (not self.junctionBox_.has_key(fromLine)):
            return False;
        toLine =    self.junctionBox_[fromLine];
        return self.doIsWired(fromLine, toLine)        
    
    def isHalfWired(self, fromLine):
        self.fixup(fromLine);
        if (not self.junctionBox_.has_key(fromLine)):
            return False;
        toLine = self.junctionBox_[fromLine]
        if (not self.junctionBox_.has_key(toLine) or self.junctionBox_[toLine] != fromLine):
            return True;
        else:
            return False;
            
    def isHalfWiredTo(self, fromLine, toLine):
        if (self.isHalfWired(fromLine) and self.junctionBox_[fromLine] == toLine):
            return True;
        return False    
            
    def isNotWired(self, fromLine):
        self.fixup(fromLine);
        if (not self.junctionBox_.has_key(fromLine)):
            return True;
        return False;           

    def shouldClean(self):
        global CLEAN_MEMORY_USER_THRESHOLD;
        global CLEAN_INTERVAL;
        if (time.time() - self.lastClean_ >= CLEAN_INTERVAL or self.count() > CLEAN_MEMORY_USER_THRESHOLD):
        #    logging.info('clean' + str(time.time()) + ">>" + str(self.lastClean_))
            return True;
        else:
            return False;

    def shouldCleanAll(self):
        global CLEAN_MEMORY_USER_THRESHOLD;
        global CLEAN_INTERVAL;
        if (self.count() > CLEAN_MEMORY_USER_THRESHOLD):
        #    logging.info('cleanAll' + str(time.time()) + ">>" + str(self.lastCleanAll_))
            return True;
        else:
            return False;            
            
    def clean(self, shouldCleanAll):
        self.notifyContentChanged();
        self.lastClean_ = time.time();
        if (shouldCleanAll):
            self.userMap_.clear();
            self.junctionBox_.clear();
            self.lastCleanAll_ = time.time();
        else:
            for key in self.userMap_.keys():
                if (self.getUser(key).isDead()):
                    del self.userMap_[key]
                    self.fixup(key);
                    
    def notifyContentChanged(self, status=True):
        self.contentChanged_ = status
        
    def contentChanged(self):
        return self.contentChanged_;
        
    def version(self):
        return self.version_;
    
    def setVersion(self, version):
        self.version_ = version
    
    def printAll(self):
        msg = "Current Version " + str(self.version_) + "<BR>\n";
        msg = msg + "User MAP=======================<BR>\n"
        for u in self.userMap_.keys():
            msg = msg + str(u);
            msg = msg + ": ";
            msg = msg + self.userMap_[u].token();
            msg = msg + ": ";
            msg = msg + "<BR>";
        msg = msg + "<BR>junctionBOX==============<BR>\n";
        for u in self.junctionBox_.keys():
            msg = msg + str(u);
            msg = msg + ": ";
            msg = msg + str(self.junctionBox_[u]);
            msg = msg + "<BR>\n";        
        return msg; 

        

class DeepantHandler:
    def getRequestHeader(self, name): abstract
    def getQuery(self): abstract
    def getRealPath(self): abstract
    def getRequestBody(self): abstract
    def writeResponseStatus(self, status):abstract
    def writeHeader(self, name, value): abstract
    def endWriteHeader(self): abstract
    def writeBody(self, data): abstract

    def Log(self, msg):
        pass;
    def onStart(self):
        pass;
    def onFinish(self):
        pass;
    def serveStaticFile(self):
        try:
            f = open(path.dirname(__file__)  + path.sep + "static"  + self.getRealPath(), 'rb');
        except:
            f = open(path.dirname(__file__)  + path.sep + "static"   + self.getRealPath() + ".html",'rb');
        self.Log(path.dirname(__file__)  + path.sep + "static"  + self.getRealPath());
            
        self.writeResponseStatus(200)
        body = f.read();
        if (self.getRealPath().endswith(".ogg")):
            self.writeHeader('Content-Type', 'video/ogg')
            self.writeHeader('max-age', '31536000')
            self.writeHeader('Expires', "Fri, 01-Jan-2099 00:00:00 GMT")
            self.writeHeader('Cache-Control', "max-age=311040000")
            self.writeHeader('Accept-Ranges', 'bytes')
            self.writeHeader('Content-Length', str(len(body)))
            self.writeHeader('Content-Range', "bytes 0-" + str(len(body) - 1) + "/" + str(len(body)))
        elif (self.getRealPath().endswith(".mp3")):
            self.writeHeader('Content-Type', 'audio/mpeg')
            self.writeHeader('Accept-Ranges', 'bytes')
            self.writeHeader('max-age', '31536000')
            self.writeHeader('Cache-Control', "max-age=311040000")
            self.writeHeader('Expires', "Fri, 01-Jan-2099 00:00:00 GMT")
            self.writeHeader('Content-Length', str(len(body)))
            self.writeHeader('Content-Range', "bytes 0-" + str(len(body) - 1) + "/" + str(len(body)))
        else:
            self.writeHeader('Content-Type', 'text/html; charset=UTF-8')
        self.endWriteHeader()
        self.writeBody(body)
        f.close()
        return 
    
    def handlerGet(self):
        return self.do_server(dict());

    def handlerPost(self):
        post_body = self.getRequestBody();
        body = parse_qs(post_body);
        return self.do_server(body);
    
    def startProfile(self):
        self.startProfile_ = time.time();
        
    def endProfile(self, id=0):
        self.Log("TIME " + str(id) + ": " + str(time.time() - self.startProfile_))

    def do_server(self, data):
#        try:
            real_path = self.getRealPath();
            query = self.getQuery();
            if (real_path.endswith(".html") or real_path.endswith(".js") or query == "" or real_path.endswith(".ogg") or real_path.endswith(".mp3")):
                self.serveStaticFile();
                return
            if real_path.endswith("dochat"):
                self.writeResponseStatus(200)
                self.writeHeader('Content-Type', 'text/html; charset=UTF-8')
                self.writeHeader("Expires", "0");
                params = parse_qs(query);
                if (params.has_key("action")):
                    action = params["action"][0];
                    self.onStart();
                    self.dispatch(action, params, data)
                    self.onFinish();
                else:
                    self.endWriteHeader()
                    self.writeBody("");
                return
            return
                
#        except IOError:
#            self.writeResponseStatus(404);
#            self.Log('File Not Found: ' + self.getRealPath())
 
    def dispatch(self, action, params, body):
        global SILENCE
        global NOT_BUSY
        data = "";
        id = 0;
        if (body.has_key("msg")):
            data = body["msg"][0];
   
        
        collection = UserCollection.getInstance();  
        if (collection.shouldCleanAll()):
            collection.clean(True);
        elif(collection.shouldClean()):
            collection.clean(False);

        if (action == "createline"):
            phone = random.randrange(1000,9999)
            token = str(uuid.uuid4())
            while(collection.hasUser(phone) == True):
                phone = random.randrange(1000,9999)
            if (collection.isFull()):
                self.writeBody("!FAIL the system is over capacity, please try again later");
                return ;
            user = collection.createUser(phone, token);
            self.endWriteHeader()
            self.writeBody(str(phone) + ":" + token);
            return ;
            
        self.endWriteHeader()
        token = "";
        if (params.has_key("token")):
            token = params["token"][0];
       
        if (token == None or token == ""):
            self.writeBody("LINEERROR");
            self.Log("LINERRORERROR canoit get token" + token);
            return ;

        fromLine = 0
        toLine = 0
        try:
            if (params.has_key("from")):
                    fromLine = string.atoi(params["from"][0])
            if (params.has_key("to")):
                    toLine = string.atoi(params["to"][0])
        except:
            fromLine = 0;
            toLine = 0;
                
        if (fromLine == 0):
            self.writeBody("LINEERROR");
            return ;
        
        if (True != collection.hasUser(fromLine)):
            self.writeBody("LINEERROR");
            return ;
        if (token != collection.getUser(fromLine).token()):
            self.writeBody("LINEERROR");
            self.Log("LINERRORERROR"+ token + "!! " + collection.getUser(fromLine).token());
            return ;
        
        if (action == "keepalive"):
            collection.getUser(fromLine).keepAlive();
            return;
        elif (action == "connect"):
            collection.unWire(fromLine);
            if (not collection.hasUser(toLine) or not collection.hasUser(fromLine)):
                self.writeBody("!FAIL user offline");
                return ;
            if (collection.isWired(toLine)):
                self.writeBody("!FAIL BUSY LINE");
                return 
            if (True == collection.oneWayWire(fromLine, toLine)):
                collection.getUser(toLine).ring(fromLine)
                self.writeBody("!SUCCESS successfully one way wired");
            else:
                self.writeBody("!FAIL unable to wire");
            return ;    
        elif (action == "disconnect"):
            if (not collection.hasUser(fromLine)):
                self.writeBody("!FAIL user offline");
                return ;
            collection.unWire(fromLine);
            if (collection.isHalfWiredTo(toLine, fromLine)):
                collection.getUser(toLine).notifyBusy(fromLine);
            self.writeBody("!SUCCESS disconnect");
            return ;
        elif (action == "querystatus"):
            ring = collection.getUser(fromLine).fetchRing()
            busy = collection.getUser(fromLine).fetchNotifyBusy()
            if (collection.isWired(fromLine) and collection.getUser(fromLine).hasNewMessage()):
                msg = "NEWMESSAGE";
                msg = msg + collection.getUser(fromLine).read();
                self.writeBody(msg);
                return ;
            elif (collection.isWired(fromLine)):
                self.writeBody("CONNECTED");
                return ;
            elif (collection.isHalfWired(fromLine) and  busy != NOT_BUSY):
                self.writeBody("BUSY:"+str(busy))
                return ;
            elif (collection.isHalfWired(fromLine)):
                self.writeBody("CONNECTING");
                return ;            
            elif (collection.isNotWired(fromLine) and ring != SILENCE):
                self.writeBody("RINGING:" + str(ring));
                return ;
            elif (collection.isNotWired(fromLine)):
                self.writeBody("DISCONNECTED");
                return ;            
            
        elif (action == "sendmsg"):
            toLine = string.atoi(params["to"][0])
            if (not collection.hasUser(fromLine) or not collection.hasUser(toLine) ):
                self.writeBody("!FAIL line error");
                return ;  
            if (data != ""):
                msg = data;
            else:    
                msg = params["msg"][0]
            if (collection.doIsWired(fromLine, toLine)):
                collection.getUser(toLine).write(msg);
                self.writeBody("!SUCCESS sendmsg");
            else:
                self.writeBody("!FAIL sendmsg");    
            
        elif (action == "getmsg"):
            if (not collection.hasUser(fromLine) ):
                self.writeBody("");
                return ;             
            msg = collection.getUser(fromLine).read();
            self.writeBody(msg);
          
        else :
            self.writeResponseStatus(501)
            self.writeBody("!FAIL not support " + action);       
        return 